<?php

class Response
{
	CONST CACHE_PREFIX = 'agilet.response';
	/**
	 * @var Response
	 */
	static private $_initiator = null;

	/**
	 * @var array
	 */
	protected $_headVars = array();

	/**
	 * @var string theme name
	 */
	protected $_theme = 'default';

	/**
	 * @var string style name of theme
	 */
	protected $_style = 'default';

	/**
	 * @var array
	 */
	public $jsConfig = array();

	/**
	 * @param string $name
	 * @param mixed $default
	 * @return mixed
	 */
	public function getConfig($key, $default = null)
	{
		static $config = NULL;

		if (NULL === $config)
		{
			$config = Config::instance() -> read('site');
		}

		if (isset($config[$key]))
		{
			return $config[$key];
		}
		return $default;
	}

	/**
	 * @param string $name
	 * @return array
	 */
	public function getHeadVar($key)
	{
		return isset($this -> _headVars[$key]) ? $this -> _headVars[$key] : array();
	}

	/**
	 * get current theme name
	 * @return string
	 */
	public function getTheme()
	{
		return $this -> _theme;
	}

	/**
	 * get current style name
	 * @return string
	 */
	public function getStyle()
	{
		return $this -> _style;
	}

	/**
	 * @param string $theme theme name
	 * @return Response
	 */
	public function setTheme($theme)
	{
		$this -> _theme = (string)$theme;
		return $this;
	}

	/**
	 * @param string $style
	 * @return Response
	 */
	public function setStyle($style)
	{
		$this -> _style = (string)$style;
	}

	/**
	 * avoid public constructor
	 */
	protected function __construct()
	{

	}

	/**
	 * get initiator object
	 * @return Response
	 */
	static public function initiator()
	{
		if (null == self::$_initiator)
		{
			self::$_initiator = new self;
		}
		return self::$_initiator;
	}

	/**
	 * Response::redirect('admin/log',array(id=>1))
	 * @param string $path
	 * @param array|string $params optional = null
	 * @param string $basePath optional = null
	 * @return void
	 */
	static public function redirect($path, $params = null, $host = null)
	{
		self::setHeader('location', Agilet::getUrl($path, $params, $host));
		exit ;
	}

	/**
	 * set header param
	 * Response::setHeader('location','http://www.google.com.vn')
	 *
	 * @param string $header_name
	 * @param string $header_value
	 * @return void
	 */
	static public function setHeader($header_name, $header_value)
	{
		header((string)$header_name . ':' . (string)$header_value);
	}

	/**
	 * set list of header params
	 * Response::setHeader(array('status code'))
	 * @param array $params
	 * @return void
	 */
	static public function setHeaders($params)
	{
		foreach ($params as $header_name => $header_value)
		{
			header((string)$header_name . ':' . (string)$header_value);
		}
		return $this;
	}

	/**
	 * execute main requestion
	 * concept about template will be remove from here.
	 * move to response object
	 * @return string
	 */
	static public function execute()
	{
		$request = Request::initiator();

		try
		{
			$request -> execute($initial = TRUE);
		}
		catch(Exception $e)
		{
			$request -> execute('error/fatal');
		}

		// process with layout container.

		$path = $request -> getPath();

		$format = $request -> getParam('format');

		if ($format == 'xml' or $format == 'js')
		{
			return $request -> response;
		}
		else
		if ($format == 'json' or $format == 'jsonp' or $request -> isAjax())
		{
			return json_encode(array(
				'html' => $request -> response,
				'error' => 0,
				'message' => ''
			));
		}
		else
		{
			return self::initiator() -> _executeFullPage($path, $format);
		}
	}

	protected function _executeFullPage($path, $format = NULL)
	{

		$layouts = array();

		$sections = array(
			'header',
			'content',
			'footer'
		);

		foreach ($sections as $section)
		{
			$layouts[$section] = $this -> _executeSection($section, $path);
		}

		$xhtml = implode('<div class="clearfix"></div>', $layouts);

		$page = '<html>';
		$page .= '<head>';
		$page .= $this -> _executeHead();
		$page .= '</head>';
		$page .= '<body><div id="site">' . $xhtml . '</div></body>';
		$page .= '</html>';

		return $page;
	}

	protected function _executeSection($section, $path)
	{
		$xhtml = array();

		$layout = $this -> loadSection($section, $path);

		$regions = $layout['regions'];

		//each region need to work as action and some thing else.

		foreach ($regions as $region_name => $elements)
		{
			$xhtml[$region_name] = $this -> _executeRegion($region_name, $elements);
		}

		$render = isset($layout['layout']) ? $layout['layout'] : null;

		if (!$render)
		{
			$render = 'layout1';
		}

		$render = 'Render_' . ucfirst($render);

		$render = new $render;

		$xhtml = $render -> execute($xhtml);

		$xhtml = '<div class="lay-' . $section . '">' . $xhtml . '</div>';

		return $xhtml;
	}

	public function _executeRegion($location, $elements)
	{
		$xhtml = '';

		foreach ($elements as $element)
		{
			// execute each region to make sure.
			try
			{

				$path = isset($element['path']) ? $element['path'] : null;

				if (null == $path)
				{
					continue;
				}

				$params = isset($element['params']) ? $element['params'] : array();

				$cls = str_replace('/', '-', $path);

				$request = Request::factory($path, $params);

				// SKIP THIS REQUEST
				if ($request -> execute($initial = FALSE) === FALSE)
				{
					continue;
				}

				$xhtml .= '<div class="' . $cls . '">' . $request -> response . '</div>';

				unset($request);

			}
			catch(Exception $e)
			{
				Logger::instance() -> write($e -> getMessage());
				if (APP_ENV == 'development')
				{
					echo $e -> getMessage();
				}
			}
		}
		return '<div class="loc-' . $location . '">' . $xhtml . '</div>';
	}

	public function & buildThemeData($theme)
	{
		$table = Model::getTable('core/appearance/section');

		$select = $table -> select() -> from($table -> info('name'), array(
			'section_id',
			'section',
			'path'
		)) -> where('theme=?', $theme) -> where('is_custom=0');

		$rows = $table -> fetchAll($select) -> toArray();

		$data = array();

		foreach ($rows as $row)
		{
			$data[sprintf('%s:%s', $row['section'], $row['path'])] = $row['section_id'];
		}

		if (empty($data))
		{
			trigger_error(__('load theme data "{theme}"', array('{theme}' => $theme)));
		}

		return $data;
	}

	public function & buildSectionData($section_id)
	{

		$table = Model::getTable('core/appearance/section');

		$obj_section = $table -> find($section_id) -> current();

		if (null == $obj_section)
		{
			throw new Agilet_Exception('Can not load block data section_id: {id}', array('{id}' => $section_id));
		}

		// reprocess this type of data.
		$table = Model::getTable('core/appearance/block');

		$select = $table -> select() -> where('section_id=?', $section_id) -> where('is_active=1') -> order('sort_order');

		$temp_items = $table -> fetchAll($select) -> toArray();

		// final list of items.
		$items = array();

		// map parent_item_id => parent_id
		foreach ($temp_items as $item)
		{
			$items[$item['id']] = $item;
		}

		unset($temp_items);

		for ($i = 0; $i < 10; ++$i)
		{

			// define some thing else.
			$map_p2c = array();
			$found = false;

			// map parent_item_id => parent_id
			foreach ($items as $item)
			{
				$map_p2c[$item['parent_id']] = 1;
			}

			foreach ($items as $key => $item)
			{
				$id = $item['id'];
				$pid = $item['parent_id'];

				// process p1.
				if (0 == $pid)
				{
					continue;
				}

				if (!isset($map_p2c[$id]) && $map_p2c[$pid] == 1)
				{
					$found = true;
					if (isset($items[$pid]) && is_array($items[$pid]))
					{
						$items[$pid]['items'][] = $item;
					}
					unset($items[$key]);
				}
				else
				{
					// temporary block parent node.
					$map_p2c[$pid] = 2;
				}
			}
			if (!$found)
			{
				break;
			}
		}

		$regions = array();

		foreach ($items as $key => $item)
		{
			if ($item['parent_id'] != 0)
			{
				unset($items[$key]);
			}
			else
			{
				$regions[$item['region']][] = $item;
			}
		}

		unset($items);
		$data = $obj_section -> toArray();
		$data['regions'] = $regions;

		return $data;
	}

	public function loadSection($section, $path, $resource = null)
	{
		static $sections;

		$theme = $this -> getTheme();

		$cache = Cache::instance();

		if (null == $sections)
		{
			$cache_key = self::CACHE_PREFIX . $theme;

			if (false == ($sections = $cache -> get($cache_key)))
			{
				$sections = $this -> buildThemeData($theme);
			}

			$cache -> set($cache_key, $sections);
		}

		if (strpos($path, '@'))
		{
			list($path, $resource) = explode('@', $path);
		}

		$section_id = NULL;

		// build a quick check from path.
		do
		{
			$check_key = sprintf('%s:%s', $section, $path);

			if (isset($sections[$check_key]))
			{
				$section_id = $sections[$check_key];
				break;
			}

			if (0 < ($pos = strrpos($path, '/')))
			{
				$path = substr($path, 0, $pos);
			}
			else
			if ($path)
			{
				$path = '';
			}
			else
			{
				$path = -1;
			}

		}
		while($path != -1);

		$cache_key = self::CACHE_PREFIX . $section_id;

		if (false == ($data = $cache -> get($cache_key)))
		{
			$data = $this -> buildSectionData($section_id);
		}

		$cache -> set($cache_key, $data);

		return $data;

	}

	/**
	 * @return string
	 */
	public function _executeHead()
	{

		$theme = $this -> getTheme();

		$staticBaseUrl = Agilet::getStaticBaseUrl();

		/**
		 * process some thing else as content or not of page.
		 */
		$this -> addMeta(array(
			'http-equiv' => 'content-type',
			'content' => 'text/html; charset=utf-8'
		)) -> addMeta(array(
			'http-equiv' => 'content-language',
			'content' => 'en-us',
		)) -> addMeta(array(
			'name' => 'revisit-after',
			'content' => $this -> getConfig('revisit-after', '1 days'),
		)) -> addMeta(array(
			'name' => 'robots',
			'content' => $this -> getConfig('robots', 'robots.txt')
		)) -> addMeta(array(
			'name' => 'owner',
			'content' => $this -> getConfig('owner')
		)) -> addMeta(array(
			'name' => 'author',
			'content' => $this -> getConfig('author')
		)) -> addMeta(array(
			'name' => 'generator',
			'content' => $this -> getConfig('generator')
		)) -> addLink(array(
			'rel' => 'shortcut icon',
			'type' => 'image/x-icon',
			'href' => $staticBaseUrl . '/favicon.ico'
		));

		$this -> prependCssLink($staticBaseUrl . 'theme/' . $theme . '/styles/all.css');
		// add main js
		$this -> prependJsLink($staticBaseUrl . 'script/main.js');
		$this -> prependJsLink($staticBaseUrl . 'script/jquery-1.7.2.min.js');

		/**
		 * @var array
		 */
		$allows = array(
			'Base',
			'Link',
			'CssLink',
			'Meta',
			'Keyword',
			'Description',
			'Title',
			'Css',
			'JsConfig',
			'JsLink',
			'Script'
		);

		/**
		 * @var array
		 */
		$xhtml = array();

		foreach ($allows as $allow)
		{
			if (($html = $this -> {'_renderHead' . $allow}()) != '')
			{
				$xhtml[] = $html;
			}
		}
		return implode(PHP_EOL, $xhtml);
	}

	public function addCssLink($value)
	{
		return $this -> __processHead('add', 'CssLink', $value);
	}

	public function appendCssLink($value)
	{
		return $this -> __processHead('append', 'CssLink', $value);
	}

	public function prependCssLink($value)
	{
		return $this -> __processHead('prepend', 'CssLink', $value);
	}

	public function addJsLink($value)
	{
		return $this -> __processHead('add', 'JsLink', $value);
	}

	public function appendJsLink($value)
	{
		return $this -> __processHead('append', 'JsLink', $value);
	}

	public function prependJsLink($value)
	{
		return $this -> __processHead('prepend', 'JsLink', $value);
	}

	public function addLink($value)
	{
		return $this -> __processHead('add', 'Link', $value);
	}

	public function appendLink($value)
	{
		return $this -> __processHead('append', 'Link', $value);
	}

	public function prependLink($value)
	{
		return $this -> __processHead('prepend', 'Link', $value);
	}

	public function addScript($value)
	{
		return $this -> __processHead('add', 'Script', $value);
	}

	public function appendScript($value)
	{
		return $this -> __processHead('append', 'Script', $value);
	}

	public function prependScript($value)
	{
		return $this -> __processHead('prepend', 'Script', $value);
	}

	public function addMeta($value)
	{
		$this -> _headVars['Meta'][] = $value;
		return $this;
	}

	public function appendMeta($value)
	{
		return $this -> __processHead('append', 'Meta', $value);
	}

	public function prependMeta($value)
	{
		return $this -> __processHead('prepend', 'Meta', $value);
	}

	public function addTitle($value)
	{
		return $this -> __processHead('add', 'Title', $value);
	}

	public function appendTitle($value)
	{
		return $this -> __processHead('append', 'Title', $value);
	}

	public function prependTitle($value)
	{
		return $this -> __processHead('prepend', 'Title', $value);
	}

	public function addKeyword($value)
	{
		return $this -> __processHead('add', 'Keyword', $value);
	}

	public function appendKeyword($value)
	{
		return $this -> __processHead('append', 'Keyword', $value);
	}

	public function prependKeyword($value)
	{
		return $this -> __processHead('prepend', 'Keyword', $value);
	}

	public function addDescription($value)
	{
		return $this -> __processHead('add', 'Description', $value);
	}

	public function appendDescription($value)
	{
		return $this -> __processHead('append', 'Description', $value);
	}

	public function prependDescription($value)
	{
		return $this -> __processHead('prepend', 'Description', $value);
	}

	/**
	 * placement
	 * @param string $name
	 * @param string/array $value
	 * @return mixed
	 */
	protected function __processHead($action, $key, $value = null)
	{

		if (!isset($this -> _headVars[$key]))
		{
			$this -> _headVars[$key] = array();
		}

		if (method_exists($this, $method = '_correct' . $key))
		{
			$value = $this -> {$method}($value);
		}

		switch($action)
		{
			case 'append' :
			case 'add' :
				$this -> _headVars[$key][] = $value;
				return $this;
			case 'prepend' :
				$this -> _headVars[$key] = array_reverse($this -> _headVars[$key]);
				$this -> _headVars[$key][] = $value;
				$this -> _headVars[$key] = array_reverse($this -> _headVars[$key]);
				return $this;
			case 'clear' :
				$this -> _headVars[$key] = array();
				return $this;
			case 'set' :
				$this -> _headVars[$key] = array($value);
			case 'has' :
				return isset($this -> _headVars[$key]) && !empty($this -> _headVars[$key]);
			case 'none' :
		}
		return $this;
	}

	/**
	 * match action
	 * @param string $name
	 * @param string $option
	 * @param string $default
	 */
	protected function __empty($name, $option, $default = '')
	{

		$value = $this -> getConfig($option, $default = '');

		if (isset($this -> _vars[$name]) && !empty($this -> _vars[$name]))
		{
			$this -> process($this -> getConfig($option . '-option', ' append'), $name, $value);
		}
		else
		{
			$this -> process('set', $name, $value);
		}
		return $this;
	}

	static protected function __flat($array)
	{
		$xhtml = array();
		if (is_array($array))
		{
			foreach ($array as $key => $value)
			{
				$xhtml[] = sprintf('%s="%s"', $key, $value);
			}
		}

		return implode(' ', $xhtml);
	}

	/**
	 * get html from current tile
	 * @return
	 */
	protected function _renderHeadTitle()
	{
		$vars = $this -> getHeadVar('Title');

		$sep = $this -> getConfig('title-separate', '-');

		if (empty($vars))
		{
			$vars = array($this -> getConfig('sitename'));
		}

		return sprintf('<title>%s</title>', implode($sep, $vars));
	}

	/**
	 * @return array
	 */
	protected function _renderHeadJsConfig()
	{
		$this -> jsConfig['basePath'] = Agilet::getAppBasePath();
		$this -> jsConfig['staticUrl'] = Agilet::getStaticBaseUrl();
		$js = json_encode($this -> jsConfig);
		return '<script type="text/javascript">var _config =' . $js . ';</script>';
	}

	/**
	 * flatten meta to html
	 * @return string
	 */
	protected function _renderHeadMeta()
	{
		$xhtml = array();

		foreach ($this->getHeadVar('Meta') as $meta)
		{
			$xhtml[] = '<meta ' . $this -> __flat($meta) . '/>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * flatten keyword to html
	 * @return string
	 */
	protected function _renderHeadKeyword()
	{
		$vars = $this -> getHeadVar('Keyword');
		if (empty($keyword))
		{
			$vars = array($this -> getConfig('keyword'));
		}
		return '<meta name="keyword" content="' . implode(', ', $vars) . '"/>';
	}

	/**
	 * flatten description to html
	 * @return string
	 */
	protected function _renderHeadDescription()
	{
		$vars = $this -> getHeadVar('Description');
		if (empty($keyword))
		{
			$vars = array($this -> getConfig('description'));
		}
		return '<meta name="description" content="' . implode(', ', $vars) . '"/>';
	}

	/**
	 * flatten link to html
	 * @return string
	 */
	protected function _renderHeadLink()
	{
		$xhtml = array();

		foreach ($this->getHeadVar('Link') as $value)
		{
			$xhtml[] = '<link ' . $this -> __flat($value) . '/>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * flatten css link to html
	 * @return string
	 */
	protected function _renderHeadCssLink()
	{
		$xhtml = array();

		foreach ($this->getHeadVar('CssLink') as $value)
		{
			$xhtml[] = '<link ' . $this -> __flat($value) . '/>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * flatten js link to html
	 * @return string
	 */
	protected function _renderHeadJsLink()
	{
		$xhtml = array();
		foreach ($this->getHeadVar('JsLink') as $value)
		{
			$xhtml[] = '<script ' . $this -> __flat($value) . '></script>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * get base href tags
	 * @return string
	 */
	protected function _renderHeadBase()
	{
		return '<base href="' . Agilet::getUrl('/') . '"/>';
	}

	/**
	 * flatten opengrap to meta, it is helpful for social sites to know what is
	 * this
	 * site
	 * @return string
	 */
	protected function _renderHeadOpengraph()
	{
		return '';
	}

	protected function _renderHeadScript()
	{
		$script = implode(';' . PHP_EOL, $this -> getHeadVar('Script'));

		if ($script)
		{
			return '<script type="text/javascript">' . $script . '</script>';
		}

		return '';
	}

	protected function _renderHeadCss()
	{
		$style = implode(';' . PHP_EOL, $this -> getHeadVar('Css'));

		if ($style)
		{
			return '<style type="text/css">' . $style . '</style>';
		}

		return '';
	}

	protected function _correctCssLink($value)
	{
		if (is_string($value))
		{
			$value = array('href' => $value);
		}
		return array_merge(array(
			'type' => 'text/css',
			'rel' => 'stylesheet',
			'media' => 'all'
		), $value);
	}

	protected function _correctJsLink($value)
	{
		if (is_string($value))
		{
			$value = array('src' => $value);
		}
		return array_merge(array('type' => 'text/javascript'), $value);
	}

}
